package com.xuqi.W3;

import java.util.Iterator;
import java.util.LinkedList;


/**
 * Execute W3Request with priority and queue support.
 */
public class W3RequestExecutor {
	
	public final static int PRIO_HIGHEST	= 254;	// highest priority request
	public final static int PRIO_HIGHER		= 192;	// higher priority request
	public final static int PRIO_NORMAL		= 128;	// normal priority request
	public final static int PRIO_LOWER		= 64;	// lower priority request
	public final static int PRIO_LOWEST		= 1;	// lowest priority request

	public final static int PRIO_SYSTEM		= 255;	// administration thread (do not use)
	public final static int PRIO_EXIT		= 0;	// makes the thread to exit (do not use)

	private static W3RequestExecutor	s_cInstance 			= null;

	private int							m_iCompleted			= 0;
	private int							m_iMaxThreads			= 1;
	private int							m_iMaxIdleTime			= 15;	// seconds
	private LinkedList<W3WorkerThread>	m_lstActiveThreads		= null;
	private LinkedList<W3WorkerThread>	m_lstIdleThreads		= null;
	private W3RequestQueue				m_lstPendingRequests	= null;

	public static synchronized W3RequestExecutor instance() {
		if ( null == s_cInstance ) {
			s_cInstance = new W3RequestExecutor(6);
		}
		return s_cInstance;
	}

	private W3RequestExecutor(int iMaxThreads) {
		m_iMaxThreads 			= iMaxThreads;
		m_lstActiveThreads		= new LinkedList<W3WorkerThread>();
		m_lstIdleThreads		= new LinkedList<W3WorkerThread>();
		m_lstPendingRequests	= new W3RequestQueue();

//		launchManagerThread();
	}

	/**
	 * Execute the request with normal priority.
	 * 
	 * @param request
	 *            : the request to be executed.
	 * 
	 * @return Queue id of the request if successful, otherwise -1 will be
	 *         returned.
	 */
	public int execute(W3Request request)
	{
		return execute(request, PRIO_NORMAL);
	}

	/**
	 * Execute the request with a specified priority.
	 * 
	 * @param request
	 *            : the request to be executed.
	 * @param priority
	 *            : priority of the request, greater value = higher priority.
	 * 
	 * @return Queue id of the request if successful, otherwise -1 will be
	 *         returned.
	 */
	public int execute(W3Request request, int priority)
	{
		int iQueueID = 0;

		synchronized (m_lstPendingRequests)
		{
			iQueueID = pushRequest(request, priority);
			if (W3RequestQueue.INVALID_QUEUE_ID != iQueueID)
			{
				launchNewThread();
			}
		}

		return iQueueID;
	}

	/**
	 * Get total thread count.
	 * 
	 * @return Thread count, including both idle threads and active threads.
	 */
	private synchronized int getThreadCount()
	{
		return getIdleThreadCount() + getActiveThreadCount();
	}

	/**
	 * Get count of threads in idle state.
	 * 
	 * @return Count of threads in idle state.
	 */
	private synchronized int getIdleThreadCount()
	{
		if (null == m_lstIdleThreads)
		{
			return 0;
		}
		else
		{
			return m_lstIdleThreads.size();
		}
	}

	/**
	 * Get count of threads currently executing requests.
	 * 
	 * @return Count of threads executing requests.
	 */
	private synchronized int getActiveThreadCount()
	{
		if (null == m_lstActiveThreads)
		{
			return 0;
		}
		else
		{
			return m_lstActiveThreads.size();
		}
	}

	/**
	 * Push a request into request queue.
	 * 
	 * @param request
	 *            : the request to be executed.
	 * @param priority
	 *            : priority of the request.
	 * 
	 * @note It only push the request into the task queue, no new threads will
	 *       be launched even if there's no idle threads.
	 * 
	 * @return Queue id of the request if successful, otherwise -1 will be
	 *         returned.
	 */
	private int pushRequest(W3Request request, int priority)
	{
		return m_lstPendingRequests.push(new W3ThreadTask(request, priority));
	}

	/**
	 * Remove a request from task queue.
	 * 
	 * @param iQueueID
	 *            : queue ID of the request to be removed.
	 */
	private void removeRequest(int iQueueID)
	{
		m_lstPendingRequests.remove(iQueueID);
	}

	/**
	 * Launch a new worker thread to execute requests in the queue in
	 * neccessary.
	 */
	private synchronized void launchNewThread()
	{
		if (0 == getIdleThreadCount() && getThreadCount() < m_iMaxThreads)
		{
			/* create new thread, default in active state. */
			W3WorkerThread cThread = new W3WorkerThread(new TaskWrapper());
			m_lstActiveThreads.add(cThread);
			cThread.start();
		}
	}

/*	*//**
	 * Launch the manager thread who keeps trying to get download task request
	 * from native code.
	 *//*
	private void launchManagerThread()
	{
		 create new thread, default in active state. 
		W3WorkerThread cThread = new W3WorkerThread(new ManagementTask());
		synchronized (this)
		{
			m_lstActiveThreads.add(cThread);
			cThread.start();
		}
	}

	*//**
	 * Wrapper class for dispatching CPU time to W3Lib management thread, which
	 * will keep trying to get download task request from native code and pass
	 * it to JAVA sub-system.
	 *//*
	private class ManagementTask implements Runnable
	{

		@Override
		public void run()
		{
			W3Log.d("W3 management thread started.");
			int iPriority = W3RequestExecutor.PRIO_NORMAL;
			while (W3RequestExecutor.PRIO_EXIT != iPriority)
			{
				boolean bAdded = false;
				W3Request cRequest = new W3Request(true);

				iPriority = jniW3.getTask(cRequest);
				if (W3RequestExecutor.PRIO_EXIT == iPriority)
				{
					break;
				}
				synchronized (W3RequestExecutor.this.m_lstPendingRequests)
				{
					int iQueueID = W3RequestExecutor.this.pushRequest(cRequest, iPriority);
					if (jniW3.onTaskCreated(iQueueID))
					{
						bAdded = true;
					}
					else
					{
						W3RequestExecutor.this.removeRequest(iQueueID);
					}
				}
				if (bAdded)
				{
					W3RequestExecutor.this.launchNewThread();
				}
			}

			W3Log.d("Trying to stop all worker threads...");
			synchronized (this)
			{
				Iterator<W3WorkerThread> it = m_lstIdleThreads.iterator();
				while (it.hasNext())
				{
					W3WorkerThread cThread = it.next();
					cThread.interrupt();
				}

				it = m_lstActiveThreads.iterator();
				while (it.hasNext())
				{
					W3WorkerThread cThread = it.next();
					cThread.interrupt();
				}
			}

			W3Log.d("W3 management thread ended.");
		}

	}*/

	/**
	 * Wrapper class for dispatching CPU time to W3ThreadTask.
	 * 
	 * @note Be aware that it will be executed in separate thread.
	 */
	private class TaskWrapper implements Runnable
	{

		@Override
		public void run()
		{
			W3Log.d("W3 worker thread started...");
			Thread thread = Thread.currentThread();
			try
			{
				W3ThreadTask task = W3RequestExecutor.this.m_lstPendingRequests.pop();
				while (true)
				{
					if (null != task)
					{
						task.run();
						++W3RequestExecutor.this.m_iCompleted;
						task = W3RequestExecutor.this.m_lstPendingRequests.pop(0);
					}

					if (null == task)
					{
						/* no more request to execute, enter idle state */
						if (thread instanceof W3WorkerThread)
						{
							synchronized (W3RequestExecutor.this)
							{
								W3RequestExecutor.this.m_lstActiveThreads.remove(thread);
								W3RequestExecutor.this.m_lstIdleThreads.add((W3WorkerThread) thread);
							}
						}
						else
						{
							W3Log.e("Invalid thread object.");
							break;
						}

						/* wait for incoming request */
						task = W3RequestExecutor.this.m_lstPendingRequests.pop(W3RequestExecutor.this.m_iMaxIdleTime);
						if (null != task)
						{
							if (thread instanceof W3WorkerThread)
							{
								synchronized (W3RequestExecutor.this)
								{
									W3RequestExecutor.this.m_lstIdleThreads.remove(thread);
									W3RequestExecutor.this.m_lstActiveThreads.add((W3WorkerThread) thread);
								}
							}
							else
							{
								W3Log.e("Invalid thread object.");
							}
						}
						else
						{
							/* timeout */
							if (thread instanceof W3WorkerThread)
							{
								synchronized (W3RequestExecutor.this)
								{
									W3RequestExecutor.this.m_lstIdleThreads.remove(thread);
								}
							}
							else
							{
								W3Log.e("Invalid thread object.");
							}
							break;
						}
					}
				}
			} catch (InterruptedException e)
			{
				// do nothing but quit
			}

			/* idle timeout, exit thread */
			W3Log.d("W3 worker thread ended...");
		}
	}
}
